use std::{env, fmt};
use std::sync::RwLock;

use anyhow::Context;
use chrono::Local;
use log::info;
use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::func::{generate_random_string, generate_random_numeric_string};
use wecom_crypto::{Agent, Source};

// 消息类型
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MessageType {
    Text,
    Image,
    Voice,
    Video,
    ShortVideo,
    Location,
    Link,
}

// 文本消息
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct TextMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: MessageType,
    pub content: String,
    pub msg_id: i64,
    pub msg_data_id: Option<String>,
    pub idx: Option<String>,
}

// 图片消息
pub struct ImageMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: MessageType,
    pub pic_url: String,
    pub media_id: String,
    pub msg_id: i64,
    pub msg_data_id: Option<String>,
    pub idx: Option<String>,
}

// 语音消息
pub struct VoiceMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: MessageType,
    pub media_id: String,
    pub format: String,
    pub msg_id: i64,
    pub msg_data_id: Option<String>,
    pub idx: Option<String>,
    pub media_id16_k: String,
}

// 视频消息
pub struct VideoMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: MessageType,
    pub media_id: String,
    pub thumb_media_id: String,
    pub msg_id: i64,
    pub msg_data_id: Option<String>,
    pub idx: Option<String>,
}

// 小视频消息
pub struct ShortVideoMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: MessageType,
    pub media_id: String,
    pub thumb_media_id: String,
    pub msg_id: i64,
    pub msg_data_id: Option<String>,
    pub idx: Option<String>,
}

// 地理位置消息
pub struct LocationMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: MessageType,
    pub location_x: String,
    pub location_y: String,
    pub scale: i32,
    pub label: String,
    pub msg_id: i64,
    pub msg_data_id: Option<String>,
    pub idx: Option<String>,
}

// 链接消息
pub struct LinkMessage {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: MessageType,
    pub title: String,
    pub description: String,
    pub url: String,
    pub msg_id: i64,
    pub msg_data_id: Option<String>,
    pub idx: Option<String>,
}



pub trait ToXml {
    fn to_xml(&self) -> String;
}

// 文本响应
#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
#[serde(rename = "xml")]
pub struct TextResponse {
    pub to_user_name: String,
    pub from_user_name: String,
    pub create_time: i64,
    pub msg_type: MessageType,
    pub content: String,
}
impl TextResponse {
    pub fn new(to: &str, from: &str, content: &str) -> Self {
        Self { to_user_name: to.to_string(), from_user_name: from.to_string(), create_time: Local::now().timestamp(), msg_type: MessageType::Text, content: content.to_string() }
    }
}
impl ToXml for TextResponse {
    fn to_xml(&self) -> String {
        format!("<xml><ToUserName><![CDATA[{}]]></ToUserName><FromUserName><![CDATA[{}]]></FromUserName><CreateTime>{}</CreateTime><MsgType><![CDATA[{}]]></MsgType><Content><![CDATA[{}]]></Content></xml>", self.to_user_name, self.from_user_name, self.create_time, "text", self.content)
    }
}

#[derive(serde::Deserialize)]
#[serde(rename_all = "PascalCase")]
#[serde(rename = "xml")]
pub struct EncryptRequestData {
    pub to_user_name: String,
    pub encrypt: String,
}

#[derive(Serialize)]
#[serde(rename_all = "PascalCase")]
#[serde(rename = "xml")]
pub struct EncryptResponseData {
    pub encrypt: String,
    pub msg_signature: String,
    pub time_stamp: i64,
    pub nonce: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct WxResp {
    pub errcode: i32,
    pub errmsg: String,
}

#[derive(Debug)]
pub enum WxError {
    InvalidSignature,
    InvalidAppid,
    WecomDecode(String),
    Internal(anyhow::Error),
}

impl fmt::Display for WxError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            WxError::InvalidSignature => write!(f, "Invalid signature"),
            WxError::InvalidAppid => write!(f, "Invalid appid"),
            WxError::Internal(e) => write!(f, "Internal error: {}", e),
            WxError::WecomDecode(e) => write!(f, "WecomDecode error: {}", e),
        }
    }
}

impl<E> From<E> for WxError
where E: Into<anyhow::Error>,
{
    fn from(error: E) -> WxError {
        WxError::Internal(error.into())
    }
}



#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AccessToken {
    #[serde(rename = "access_token")]
    pub token: String,      // 获取到的凭证
    pub expires_in: i64,    // 凭证有效时间，单位：秒
}


pub struct AccessTokenGetter {
    url: String,
    access_token: RwLock<Option<AccessToken>>,
}

impl AccessTokenGetter {
    pub fn new(url: String) -> Self {
        Self { url, access_token: RwLock::new(Option::None) }
    }

    pub async fn get_token(&self) -> Result<String, WxError> {
        let tmp = self.access_token.read().unwrap().clone();

        if let Some(token) = tmp {
            // 判断是否超时，如果未超时直接返回AccessToken里面的token
            let now = Local::now().timestamp();
            if token.expires_in > now {
                return Ok(token.token);
            }
        }

        let resp = reqwest::get(&self.url).await?;
        let text = resp.text().await?;
        let access_token: AccessToken = serde_json::from_str(&text).with_context(|| format!("get_access_token error: {}", text))?;
        
        let mut data = self.access_token.write().unwrap();
        *data = Option::Some(access_token.clone());

        Ok(access_token.token)
    }
}

#[derive(Clone)]
pub struct WechatMp {
    appid: String,
    secret: String,
    agent: Agent,
}
impl WechatMp {
    pub fn new(token: String, appid: String, secret: String, aes_key: String) -> Self {
        let agent = Agent::new(&token, &aes_key);

        Self { appid, secret, agent }
    }

    pub fn new_from_env() -> Result<Self, WxError> {
        let appid = env::var("WECHAT_APPID").with_context(|| "WECHAT_APPID must be set!")?;
        let secret = env::var("WECHAT_SECRET").with_context(|| "WECHAT_SECRET must be set!")?;
        let token = env::var("WECHAT_TOKEN").with_context(|| "WECHAT_TOKEN must be set!")?;
        let aes_key = env::var("WECHAT_AES_KEY").with_context(|| "WECHAT_AES_KEY must be set!")?;
        
        let mp = Self::new(token, appid, secret, aes_key);
        Ok(mp)
    }

    pub fn check_signature(&self, signature: &str, timestamp: &str, nonce: &str) -> bool {
        // 检查时间戳是否在有效期内
        // let now = Local::now().timestamp();
        // if now - timestamp.parse::<i64>().unwrap() > VALID_PERIOD {
        //     return false;
        // }
        // 检查签名是否正确
        let sign = self.agent.generate_signature(&[timestamp, nonce]);
        info!("check_signature sign: {}", sign);
        info!("check_signature signature: {}", signature);
        sign == signature
    }

    pub fn access_token_getter(&self) -> AccessTokenGetter {
        let url = format!("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}", self.appid, self.secret);
        AccessTokenGetter::new(url)
    }

    pub fn decrypt_message(&self, encrypt: &str, msg_signature: &str, timestamp: &str, nonce: &str) -> Result<String, WxError> {
        let sign = self.agent.generate_signature(&[timestamp, nonce, encrypt]);
        if sign != msg_signature {
            return Err(WxError::InvalidSignature);
        }

        let source = self.agent.decrypt(encrypt)?;
        Ok(source.text)
    }

    pub fn encrypt_message<T>(&self, obj: &T) -> Result<String, WxError>
    where T: ToXml {
        let msg = obj.to_xml();

        let timestamp = Local::now().timestamp().to_string();
        let nonce = generate_random_numeric_string(8);
        let random = generate_random_string(16);
        info!("encrypt_message data: {:?}", vec![&msg, &timestamp, &nonce, &random]);

        // 加密
        let source = Source {
            text: msg,
            receive_id: self.appid.clone(),
        };
        let encrypt = self.agent.encrypt(&source);
        info!("encrypt_message encrypt: {}", encrypt);

        // 签名
        let msg_signature = self.agent.generate_signature(&[&timestamp, &nonce, &encrypt]);
        info!("encrypt_message msg_signature: {}", msg_signature);

        let resp = format!("<xml><Encrypt><![CDATA[{}]]></Encrypt><MsgSignature><![CDATA[{}]]></MsgSignature><TimeStamp>{}</TimeStamp><Nonce><![CDATA[{}]]></Nonce></xml>", encrypt, msg_signature, timestamp, nonce);
        info!("encrypt_message resp: {}", resp);
        Ok(resp)
    }

    pub async fn send_text_message(self, openid: &str, message: &str) -> Result<WxResp, WxError> {
        let token_getter = self.access_token_getter();
        let access_token = token_getter.get_token().await?;
        self.send_text_message_by_token(openid, message, &access_token).await
    }

    pub async fn send_text_message_by_token(self, openid: &str, message: &str, token: &str) -> Result<WxResp, WxError> {
        let url = format!("https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={}", token);
        let client = reqwest::Client::new();
        let json_value = json!({
            "touser":openid,
            "msgtype":"text",
            "text":
            {
                "content": message
            }
        });
        let json_str = serde_json::to_string(&json_value).expect("Failed to serialize JSON");
        let resp = client.post(url)
            .body(json_str)
            .send()
            .await?;
        let wx_resp: WxResp = resp.json().await?;
        Ok(wx_resp)
    }
}



#[cfg(test)]
mod tests {
    use std::env;

    use dotenvy::dotenv;
    use async_trait::async_trait;
    use tokio::time::sleep;
    use crate::{func::str_to_timestamp, timer2::{TaskAction, Timer, TimerError, TimestampTrigger, Trigger}};

    use super::*;

    fn setup() {
        dotenv().ok();
    }

    
    #[tokio::test]
    async fn test_mp_timer() {
        setup();
        let timer = Timer::init().await.unwrap();

        #[derive(Clone)]
        struct MpSendTextAction {
            open_id: String,
            text: String,
            mp: WechatMp,
        }
        impl MpSendTextAction {
            fn new(open_id: &str, text: &str, mp: WechatMp) -> Self {
                Self {open_id: open_id.to_owned(), text: text.to_owned(), mp}
            }
        }
        #[async_trait]
        impl TaskAction for MpSendTextAction {
            type Output = ();
            async fn execute(&self) -> Result<Self::Output, TimerError> {
                let resp = self.mp.clone().send_text_message(&self.open_id, &self.text).await.unwrap();
                println!("wxresp: {:?}", resp);
                Ok(())
            }
        }

        let sec = str_to_timestamp("2024-11-24 09:43:00", "%Y-%m-%d %H:%M:%S");
        info!("sec: {}", sec);
        let mp = WechatMp::new_from_env().unwrap();
        let trigger = Trigger::Once(TimestampTrigger { timestamp: sec as u64 });
        let action = MpSendTextAction::new("oTxzgt4_WcFLCkzrxIKn7_L4YRzU", "测试123456", mp);
        let _ = timer.add_task(trigger, action).await;

        timer.start().await.unwrap();

        sleep(std::time::Duration::from_secs(180)).await;
    }


    
    #[tokio::test]
    async fn test_send_text() -> Result<(), WxError>{
        dotenv().ok();
        let appid = env::var("WECHAT_APPID").unwrap();
        let secret = env::var("WECHAT_SECRET").unwrap();
        let token = env::var("WECHAT_TOKEN").unwrap();
        let aes_key = env::var("WECHAT_AES_KEY").unwrap();

        let wechat_mp = WechatMp::new(token, appid, secret, aes_key);
        let resp = wechat_mp.send_text_message("oTxzgt4_WcFLCkzrxIKn7_L4YRzU", "55666").await?;
        println!("wxresp: {:?}", resp);
        Ok(())
    }

    #[test]
    fn test_check_signature() {
        let wechat_mp = WechatMp::new("token".to_string(), "appid".to_string(), "secret".to_string(), "aes_key".to_string());
        assert!(wechat_mp.check_signature("signature", "timestamp", "nonce"));
    }

    #[test]
    fn test_generate_random_string() {
        let random_string = generate_random_string(16);
        println!("random_string: {}", random_string);
        assert_eq!(random_string.len(), 16);
    }

}
